package com.lianqi.emcpframework.extend.script.engine;


import com.lianqi.emcpframework.extend.script.ExecuteResult;
import com.lianqi.emcpframework.extend.script.ListenerSupportEngine;
import com.lianqi.emcpframework.extend.script.ScriptContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.DigestUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program emcp-framework
 * @ClassName SpElEngine
 * @description: spel脚本引擎定义
 * @author: sky
 * @create: 2018/12/04 19:57
 */
@Slf4j
public class SpElEngine extends ListenerSupportEngine {

    final Map<String,SpelScriptContext> cache = new ConcurrentHashMap<>();

    final ExpressionParser parser = new SpelExpressionParser();

    private List<EvaluationAdditional> additionals = new ArrayList<>();

    @Override
    public void init(String... contents) throws Exception {

    }

    @Override
    public boolean compile(String id, String script) throws Exception {

        if (log.isDebugEnabled()){
            log.debug("compile SpEL {} : {}",id,script);
        }
        cache.put(id,new SpelScriptContext(id, DigestUtils.md5DigestAsHex(script.getBytes()),
                parser.parseExpression(script)));
        return true;
    }

    @Override
    public ScriptContext getContext(String id) {
        return null;
    }

    @Override
    public boolean complied(String id) {
        return cache.containsKey(id);
    }

    @Override
    public boolean remove(String id) {
        return cache.remove(id) != null;
    }

    @Override
    public ExecuteResult excute(String id, Map<String, Object> params) {
        if (log.isDebugEnabled()){
            log.debug("excute spEL {} : {}",id,params);
        }
        ExecuteResult result = new ExecuteResult();
        long start = System.currentTimeMillis();
        SpelScriptContext context = cache.get(id);
        try {
            if (context != null){
                doListenerBefore(context);
                context = cache.get(id);
                params = new HashMap<>(params);
                params.putAll(getGlobalVariables());
                StandardEvaluationContext evaluationContext = new StandardEvaluationContext(params);
                for (Map.Entry<String,Object> entry : params.entrySet()){
                    evaluationContext.setVariable(entry.getKey(),entry.getValue());
                }
                additionals.forEach(additional -> additional.init(evaluationContext));

                Object object = context.getScript().getValue(evaluationContext);
                result.setSuccess(true);
                result.setResult(object);
            }else {
                result.setSuccess(false);
                result.setResult(null);
                result.setMessage(String.format("SpEL: %s not found!",id));
            }
            long end = System.currentTimeMillis();
            result.setUseTime(end - start);

        }catch (Exception e){
            result.setException(e);
            result.setMessage(e.getMessage());
        }
        doListenerAfter(context,result);
        return result;
    }

    @Override
    public ExecuteResult excute(String id) {
        return excute(id,new HashMap<>());
    }

    public void addEvaluationAdditional(EvaluationAdditional contextCall){
        this.additionals.add(contextCall);
    }

    /**
     * spel 脚本上下文定义
     */
    class SpelScriptContext extends ScriptContext {
        private Expression script;

        public SpelScriptContext(String id, String md5, Expression script) {
            super(id, md5);
            this.script = script;
        }

        public Expression getScript() {
            return script;
        }
    }

    /**
     * 脚本环境参数补充
     * 使用此接口的实现类 实现自定义参数补充
     */
    public interface EvaluationAdditional {
        void init(StandardEvaluationContext context);
    }
}
